#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <stdint.h>
#include <stdbool.h>
#include <linux/input.h>

enum DIR{
	UP, LEFT, DOWN, RIGHT, DEL, REF, OUT
};

// BMP格式头规范
struct bitmap_header {
	int16_t type;
	int32_t size; // 图像文件大小
	int16_t reserved1;
	int16_t reserved2;
	int32_t offbits; // bmp图像数据偏移量
} __attribute__((packed));

struct bitmap_info {
	int32_t size;   // 本结构大小
	int32_t width;  // 图像宽
	int32_t height; // 图像高
	int16_t planes;
	
	int16_t bit_count; // 色深
	int32_t compression;
	int32_t size_img; // bmp数据大小，必须是4的整数倍
	int32_t X_pel;
	int32_t Y_pel;
	int32_t clrused;
	int32_t clrImportant;
} __attribute__((packed));

// 以下结构体不一定存在于BMP文件中，除非：
// bitmap_info.compression为真
struct rgb_quad {
	int8_t blue;
	int8_t green;
	int8_t red;
	int8_t reserved;
} __attribute__((packed));


int32_t width, height, bpp, size;

// 边框颜色
int edge = 0x00888888;

// 单元格元素
int figure[4][4] = {0};

// 单元格的minX 和 maxX
int Coord_x[8] = {185, 285, 295, 395, 405, 505, 515, 615};
// 单元格的minY 和 maxY
int Coord_y[8] = {25, 125, 135, 235, 245, 345, 355, 455};
// 单元格长度
int edge_len = 100;

// 当前最高分
int grade = 0;
// 空格个数
int blank = 16;
// 是否失败
bool is_fail = false;

// 是否生成
bool is_show = true;

// 是否点击了删除
bool is_del = false;
// 是否点击了重开
bool is_ref = false;
// 是否点击了退出
bool is_out = false;

// 是否在单元格间的行间隙
bool inHorizontal(int row){
	for(int i = 1, j = 2; i <= 5; i += 2, j += 2){
		if(row >= Coord_y[i] && row < Coord_y[j]){
			return true;
		}
	}
	return false;
}

// 是否在单元格间的列间隙
bool inVertical(int row){
	for(int i = 1, j = 2; i <= 5; i += 2, j += 2){
		if(row >= Coord_x[i] && row < Coord_x[j]){
			return true;
		}
	}
	return false;
}


// 是否在单元格内
bool inCell(int col, int row, int x, int y){
	if(col >= x && col < x + edge_len && row >= y && row < y + edge_len){
		return true;
	}
	return false;
}

// 
void showBmp(char *bmp_name, char *now, int row, int col){
	int bmpFd = open(bmp_name, O_RDONLY);
	
	// 读取BMP格式头，获取图片信息
	struct bitmap_header header;
	struct bitmap_info info;
	struct rgb_quad quad;
	
	read(bmpFd, &header, sizeof(header));
	read(bmpFd, &info, sizeof(info));
	
	if (info.compression != 0) {
		read(bmpFd, &quad, sizeof(quad));
		fprintf(stderr, "read quad! \n");
	}
	
	// 读取BMP文件信息
	char *bmpbuf = malloc(3 * edge_len * edge_len);
	int lineByte = 3 * edge_len;
	read(bmpFd, bmpbuf, 3 * edge_len * edge_len);
	
	// 映射到屏幕
	for(int i = row, r = edge_len - 1; r >= 0; i++, r--){
		for(int j = col, c = 0; c < lineByte; j++, c += 3){
			int index = r * lineByte + c; 
			int blue = bmpbuf[index];
			int green = bmpbuf[index + 1];  
			int red = bmpbuf[index + 2];
			int color = 0 << 24 | (red << 16) | (green << 8) | blue;
			int offset = (i * 800 + j) * 4;
			memcpy(now + offset, &color, 4);
		}
	}
	
	close(bmpFd);
}

void showFrame(char *p){
	for(int row = 0; row < 480; row++){
		// 顶部 和 底部 边框
		if(row < Coord_y[0] || row >= Coord_y[6] + 100){
			for(int col = 160; col < 640; col++){
				memcpy(p + 4 * (row * 800 + col), &edge, 4);
				memcpy(p + 4 * (row * 800 + col) + size, &edge, 4);
			}
		}
		// 分割行 边框
		else if(inHorizontal(row)){
			for(int col = 160; col < 640; col++){
				if(inVertical(col)){
					continue;
				}
				memcpy(p + 4 * (row * 800 + col), &edge, 4);
				memcpy(p + 4 * (row * 800 + col) + size, &edge, 4);
			}
		}	
		// 数据行 边框
		else {
			for(int col = 160; col < 640; col++){
				if(inVertical(col) || col < 185 || col >= 615){
					memcpy(p + 4 * (row * 800 + col), &edge, 4);
					memcpy(p + 4 * (row * 800 + col) + size, &edge, 4);
				}
			}
		}
	}
	
	showBmp("./num/del.bmp", p, 0, 0);
	showBmp("./num/del.bmp", p + size, 0, 0);
	
	showBmp("./num/out.bmp", p, 0, 700);
	showBmp("./num/out.bmp", p + size, 0, 700);
	
	showBmp("./num/ref.bmp", p, 380, 0);
	showBmp("./num/ref.bmp", p + size, 380, 0);
}

// 获取bmp文件名
void getBmpName(int now, char *path_name){
	char name[15];
	int get_num = sprintf(name, "./num/%d.bmp", now);
	if(get_num > 0){
		strcpy(path_name, name);
	}
	return;
}

void showCell(char *now){
	blank = 0;
	for(int row = 0; row < 4; row++){
		for(int col = 0; col < 4; col++){
			if(figure[row][col] != 0){
				grade = (figure[row][col] > grade) ? figure[row][col] : grade;
				char name[15];
				getBmpName(figure[row][col], name);

				// 映射到屏幕
				showBmp(name, now, Coord_y[2 * row], Coord_x[2 * col]);
			} else {
				blank++;
				for(int i = Coord_y[2 * row]; i < Coord_y[2 * row + 1]; i++){
					for(int j = Coord_x[2 * col]; j < Coord_x[2 * col + 1]; j++){
						int color = 0x00FFFFFF;
						int offset = (i * 800 + j) * 4;
						memcpy(now + offset, &color, 4);
					}
				}
			}
		}
	}
}


// 获取用户在屏幕的指令
enum DIR getDir(){
	int tsFd = open("/dev/input/event0", O_RDWR);
	struct input_event tinfo;
	int x = 0, y = 0, tmpx = 0, tmpy = 0;
	while(1){
		int read_ret = read(tsFd, &tinfo, sizeof(tinfo));
		if(read_ret < 0){
			perror("打开触摸屏失败\n");
			break;
		}
		
		if(tinfo.type == EV_ABS){
			if(tinfo.code == ABS_X){
				tmpx = tinfo.value;
			}
			if(tinfo.code == ABS_Y){
				tmpy = tinfo.value;
			}
		}
		
		if(tinfo.type == EV_KEY && tinfo.code == BTN_TOUCH){
			if(tinfo.value > 0){
				// 从 1024 * 600 转换为 800 * 480
				x = tmpx * 25 / 32;
				y = tmpy * 4 / 5;
				printf("1、x = %d  y = %d\n", x, y);
			} else {
				// 从 1024 * 600 转换为 800 * 480
				tmpx = tmpx * 25 / 32;
				tmpy = tmpy * 4 / 5;
				
				// 点击了删除
				if(inCell(x, y, 0, 0) && inCell(tmpx, tmpy, 0, 0)){
					return DEL;
				} 
				
				// 点击了重开
				else if(inCell(x, y, 0, 380) && inCell(tmpx, tmpy, 0, 380)){
					return REF;
				} 
				
				// 点击了退出
				else if(inCell(x, y, 700, 0) && inCell(tmpx, tmpy, 700, 0)){
					return OUT;
				}
				
				// 获取按下 到 松开的坐标差
				x -= tmpx;
				y -= tmpy;
				printf("2、x = %d  y = %d\n", tmpx, tmpy);
				break;
			}
		}
	}
	
	// 关闭触摸屏
	close(tsFd);
	
	// 获取滑动方向
	if(x >= 0){
		if(y >= 0){
			return (y < x) ? LEFT : UP;
		} else {
			y *= (-1);
			return (y < x) ? LEFT : DOWN;
		}
	}else {
		x *= (-1);
		if(y >= 0){
			return (y < x) ? RIGHT : UP;
		} else {
			y *= (-1);
			return (y < x) ? RIGHT : DOWN;
		}
	}
}

// 从顶到底、从左往右，依次获取下方最近方块
// 若现方块为0，则将获取的方块移动至此
// 若现方块非0，则与获取方块比较，同则double，异则忽略
// 循环往复直至到底部
void goUp(){
	for(int col = 0; col < 4; col++){
		for(int up = 0; up < 4 ; up++){
			int down = up + 1;
			while(figure[down][col] == 0){
				down++;
				if(down >= 4){
					break;
				}
			}
			if(down < 4){
				if(figure[up][col] != 0){
					if(figure[up][col] == figure[down][col]){
						figure[up][col] *= 2;	
						figure[down][col] = 0;
						blank++;
						is_show = true;
					}
				} else {
					figure[up][col] = figure[down][col];
					figure[down][col] = 0;
					up--;
					is_show = true;
				}
			}
		}
	}
}

void goDown(){
	for(int col = 0; col < 4; col++){
		for(int down = 3; down >= 0; down--){
			int up = down - 1;
			while(figure[up][col] == 0){
				up--;
				if(up < 0){
					break;
				}
			}
			if(up >= 0){
				if(figure[down][col] != 0){
					if(figure[down][col] == figure[up][col]){
						figure[down][col] *= 2;	
						figure[up][col] = 0;
						blank++;
						is_show = true;
					}
				} else {
					figure[down][col] = figure[up][col];
					figure[up][col] = 0;
					down++;
					is_show = true;
				}
			}
		}
	}
}

void goLeft(){
	for(int row = 0; row < 4; row++){
		for(int left = 0; left < 4 ; left++){
			int right = left + 1;
			while(figure[row][right] == 0){
				right++;
				if(right >= 4){
					break;
				}
			}
			if(right < 4){
				if(figure[row][left] != 0){
					if(figure[row][left] == figure[row][right]){
						figure[row][left] *= 2;	
						figure[row][right] = 0;
						blank++;
						is_show = true;
					}
				} else {
					figure[row][left] = figure[row][right];
					figure[row][right] = 0;
					left--;
					is_show = true;
				}
			}
		}
	}
}

void goRight(){
	for(int row = 0; row < 4; row++){
		for(int right = 3; right >= 0 ; right--){
			int left = right - 1;
			while(figure[row][left] == 0){
				left--;
				if(left < 0){
					break;
				}
			}
			if(left >= 0){
				if(figure[row][right] != 0){
					if(figure[row][right] == figure[row][left]){
						figure[row][right] *= 2;	
						figure[row][left] = 0;
						blank++;
						is_show = true;
					}
				} else {
					figure[row][right] = figure[row][left];
					figure[row][left] = 0;
					right++;
					is_show = true;
				}
			}
		}
	}
}

// 删除格子元素
void goDel(){
	int tsFd = open("/dev/input/event0", O_RDWR);
	struct input_event tinfo;
	int x = 0, y = 0, tmpx = 0, tmpy = 0;
	while(1){
		int read_ret = read(tsFd, &tinfo, sizeof(tinfo));
		if(read_ret < 0){
			perror("打开触摸屏失败\n");
			break;
		}
		
		if(tinfo.type == EV_ABS){
			if(tinfo.code == ABS_X){
				tmpx = tinfo.value;
			}
			if(tinfo.code == ABS_Y){
				tmpy = tinfo.value;
			}
		}
		
		if(tinfo.type == EV_KEY && tinfo.code == BTN_TOUCH){
			if(tinfo.value > 0){
				x = tmpx * 25 / 32;
				y = tmpy * 4 / 5;
				printf("1、x = %d  y = %d\n", x, y);
			} else {
				tmpx = tmpx * 25 / 32;
				tmpy = tmpy * 4 / 5;
				
				printf("2、x = %d  y = %d\n", tmpx, tmpy);
				break;
			}
		}
	}
	
	close(tsFd);
	
	
	if(x >= 185 && tmpx >= 185 && y >= 25 && tmpy >= 25){
		x -= 185;
		tmpx -= 185;
		y -= 25;
		tmpy -= 25;
		
		x /= 115;
		tmpx /= 115;
		y /= 115;
		tmpy /= 115;
		
		printf("%d %d\n%d %d\n", x, y, tmpx, tmpy);
		
		if(x != tmpx || y!= tmpy){
			return;
		}
		if(x >= 4 || y >= 4){
			return;
		}
		
		printf("del\n");
		is_del = false;
		figure[y][x] = 0;
	}
}

void updateFig(){
	if(!is_del){
		enum DIR dir = getDir();
		is_show = false;
		switch (dir) {
		case UP:
			printf("UP\n");
			goUp();
			break;
		case LEFT:
			printf("LEFT\n");
			goLeft();
			break;
		case DOWN:
			printf("DOWN\n");
			goDown();
			break;
		case RIGHT:
			printf("RIGHT\n");
			goRight();
			break;
		case DEL:
			printf("DEL\n");
			is_del = true;
			break;
		case OUT:
			printf("OUT\n");
			is_out = true;
			break;
		case REF:
			printf("REF\n");
			is_ref = true;
			break;
			default:
				break;
		}
	} else {
		goDel();
	}
	
}

void createNum(int n){
	if(blank == 0){
		is_fail = true;
		return;
	}
	int row, col;
	do{
		row = (rand() + n) % 4;
		col = rand() % 4;
	}while(figure[row][col] != 0);
	int factor = rand() % 5;
	factor = (factor == 4) ? 4 : 2;
	figure[row][col] = factor;
	return;
}

void gameOpen(){
	for(int i = 0;i < 4; i++){
		for(int j = 0; j < 4; j++){
			figure[i][j] = 0;
		}
	}
	grade = 0;
	for(int i = 1; i <= 3;i++){
		createNum(i);
	}
	
	is_ref = false;
}


int game4_4(int lcdFd, struct fb_var_screeninfo vinfo, char *lcd, int time){
	memset(lcd, 0x00FFFFFF, 2 * size);
	vinfo.xoffset = 0;
	vinfo.yoffset = (time % 2) * 480;
	ioctl(lcdFd, FBIOPAN_DISPLAY, &vinfo);
	
	grade = 0;
	blank = 16;
	is_fail = false;
	
	is_show = true;
	
	is_del = false;
	is_ref = false;
	is_out = false;
	
	gameOpen();
	showFrame(lcd);
	for(int n = time;!is_out; n++, time = (time + 1) % 2){
		if(is_ref){
			gameOpen();
			is_show = true;
		}
		char *tmp = lcd + (n % 2) * size;
		
		if(is_show){	
			createNum(n);
		}else {
			is_fail = (blank == 0) ? true : false;
		}
		showCell(tmp);
		vinfo.xoffset = 0;
		vinfo.yoffset = 480*(n%2);
		ioctl(lcdFd, FBIOPAN_DISPLAY, &vinfo);
		
		if(is_fail){
			printf("你输了！\n");
			break;
		}
		if(grade >= 2048){
			printf("你赢了！\n");
			break;
		}
		
		updateFig();
		printf("n%d\n", n);
	}
	printf("%d 游戏已退出\n", time);
	return time;
}

int showMenu(int lcdFd, struct fb_var_screeninfo vinfo, char *lcd, int n){
	printf("n %d\n", n);
	
	// 打开图片文件
	int bmpFd = open("./num/logo1.bmp", O_RDONLY);
	if (bmpFd == -1) {
		perror("open bmp error");
		return -1;
	}
	// 读取文件头		14字节
	struct bitmap_header bfh;
	read(bmpFd, &bfh, sizeof(bfh));
	
	// 读取文件信息头	40字节
	struct bitmap_info bih;
	read(bmpFd, &bih, sizeof(bih));
	
	int width = bih.width, 
	height = bih.height, 
	bpp = bih.bit_count;
	
	int size = width * height * (bpp / 8);
	char *bmpBuf = malloc(size);
	read(bmpFd, bmpBuf, size);

	bpp /= 8;
	
	size = (n % 2 == 0) ? 0 : 800 * 480 * 4;

	for(int row = 479, i = 0; i < 480; row--, i++){
		for(int col = 0; col < 800; col++){
			int index = (row * width + col) * bpp;
			int alpha = ((bpp == 4) ? bmpBuf[index + 3] : 0x00) << 24;
			int red = bmpBuf[index + 2] << 16;
			int green = bmpBuf[index + 1] << 8;
			int blue = bmpBuf[index];
			// char 转 int
			int color = alpha | red | green | blue;
			memcpy(lcd + 4 * (i * 800 + col) + size, &color, 4);
		}
	}
	
	vinfo.xoffset = 0;
	vinfo.yoffset = (n % 2) * 480;
	ioctl(lcdFd, FBIOPAN_DISPLAY, &vinfo);
	return 0;
}

bool inBtn(int col, int row, int x0, int y0, int x1, int y1){
	if(col >= x0 && col < x1 && row >= y0 && row < y1){
		return true;
	}
	return false;
}

int main(int argc, char *argv[]){
	srand((unsigned int)time(NULL));
	
	int lcdFd = open("/dev/fb0", O_RDWR|O_EXCL);
	struct fb_var_screeninfo vinfo;
	ioctl(lcdFd, FBIOGET_VSCREENINFO, &vinfo);
	
	width = vinfo.xres;
	height = vinfo.yres;
	bpp = vinfo.bits_per_pixel;
	size = width * height * bpp/8;
	
	char *p = mmap(NULL, 2 * size, PROT_WRITE, MAP_SHARED, lcdFd, 0);
	memset(p, 0x00FFFFFF, 2 * size);
	
	vinfo.xoffset = 0;
	vinfo.yoffset = 480;
	ioctl(lcdFd, FBIOPAN_DISPLAY, &vinfo);
	
	int n = 0;
	showMenu(lcdFd, vinfo, p, n);
	
	for(;; n = (n+1) % 2){
		int tsFd = open("/dev/input/event0", O_RDWR);
		struct input_event tinfo;
		int x = 0, y = 0, tmpx = 0, tmpy = 0;
		while(1){
			int read_ret = read(tsFd, &tinfo, sizeof(tinfo));
			if(read_ret < 0){
				perror("打开触摸屏失败\n");
				break;
			}
			
			if(tinfo.type == EV_ABS){
				if(tinfo.code == ABS_X){
					tmpx = tinfo.value;
				}
				if(tinfo.code == ABS_Y){
					tmpy = tinfo.value;
				}
			}
			
			if(tinfo.type == EV_KEY && tinfo.code == BTN_TOUCH){
				if(tinfo.value > 0){
					x = tmpx * 25 / 32;
					y = tmpy * 4 / 5;
					printf("1、x = %d  y = %d\n", x, y);
				} else {
					tmpx = tmpx * 25 / 32;
					tmpy = tmpy * 4 / 5;
					
					printf("2、x = %d  y = %d\n", tmpx, tmpy);
					break;
				}
			}
		}
		
		close(tsFd);
		
		if(inBtn(x, y, 100, 300, 300, 400) && inBtn(tmpx, tmpy, 100, 300, 300, 400)){
			n = game4_4(lcdFd, vinfo, p, n);
			showMenu(lcdFd, vinfo, p, n);
			showMenu(lcdFd, vinfo, p, ++n);
		} else if(inBtn(x, y, 780, 0, 800, 20) && inBtn(tmpx, tmpy, 780, 0, 800, 20)){
			break;
		}
		
		
		
	}
	close(lcdFd);
}
